package abel.wlmr.model.type;

import java.util.HashMap;
import java.util.Map;

import abel.wlmr.model.WlmrField;

/**
 * 控制域 C 表示报文传输方向和所ᨀ供的传输服务类型的信息，定义见图 B.3。
 * D7 D6 D5 D4 D3～D0
 * 下行方向 传输方向位
 * DIR
 * 启动标志位
 * PRM
 * 帧计数位 FCB 帧计数有效位 FCV
 * 功能码
 * 上行方向 要求访问位 ACD 保留
 * 
 * @author Abel
 *
 */
public class WlmrControlField extends WlmrField {

    private CtrlDir dir;

    private CtrlPrm prm;

    private CtrlFcv fcv = CtrlFcv.Invalid;

    private CtrlFcb fcb = CtrlFcb.Invalid;

    private CtrlAcd acd = CtrlAcd.NoEvents;

    private FnActiveType fat;

    private FnProactiveType pat;

    public WlmrControlField() {
        super(1);
    }

    @Override
    public int getValue() {
        switch (prm) {
        case Active:
            if (fat == null) {
                throw new IllegalArgumentException("FN active type can't be null if active");
            }
            break;

        case Proactive:
            if (pat == null) {
                throw new IllegalArgumentException("FN proactive type can't be null if active");
            }
            break;

        default:
            throw new IllegalArgumentException("prm can't be null");
        }

        if (fcv == null) {
            throw new IllegalArgumentException("fcv can't be null");
        }

        if (dir == null) {
            throw new IllegalArgumentException("dir can't be null");
        }

        int b = 0;

        b |= (CtrlPrm.Active.equals(prm)) ? fat.getValue() : pat.getValue();

        switch (dir) {
        case Down:
            b |= fcv.getValue() << 4;
            b |= fcb.getValue() << 5;
            break;

        case Up:
            b |= acd.getValue() << 5;
            break;

        default:
            throw new IllegalArgumentException("dir can't be null");
        }

        b |= prm.getValue() << 6;
        b |= dir.getValue() << 7;

        return b;
    }

    @Override
    public void parseValue(int b) {
        dir = CtrlDir.findDirByValue((b >> 7) & 1);
        prm = CtrlPrm.findPrmByValue((b >> 6) & 1);

        switch (dir) {
        case Down:
            fcb = CtrlFcb.findFcvByValue((b >> 5) & 1);
            fcv = CtrlFcv.findFcvByValue((b >> 4) & 1);
            break;

        case Up:
            acd = CtrlAcd.findAcdByValue((b >> 5) & 1);
            break;

        default:
            break;
        }

        switch (prm) {
        case Active:
            fat = FnActiveType.findEnumByValue((byte) (b & 0x0F));
            break;

        case Proactive:
            pat = FnProactiveType.findEnumByValue((byte) (b & 0x0F));
            break;

        default:
            break;
        }
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return String.format("{dir:'%s', prm:'%s', fcv:'%s', fcb:'%s', acd:'%s', fat:'%s', pat:'%s'}",
            dir, prm, fcv, fcb, acd, fat, pat);
    }

    /**
     * B.4.3.3.2.1 传输方向位DIR
     * DIR=0：表示此帧报文是由主站发出的下行报文； DIR=1：表示此帧报文是由终端发出的上行报文。
     * 
     * @return the dir
     */
    public CtrlDir getDir() {
        return dir;
    }

    /**
     * B.4.3.3.2.1 传输方向位DIR<br/>
     * DIR=0：表示此帧报文是由主站发出的下行报文； DIR=1：表示此帧报文是由终端发出的上行报文。<br/>
     * 
     * @param dir
     *            the dir to set
     */
    public void setDir(CtrlDir dir) {
        this.dir = dir;
    }

    /**
     * B.4.3.3.2.2 启动标志位PRM<br/>
     * PRM =1：表示此帧报文来自启动站； PRM =0：表示此帧报文来自从动站。<br/>
     * 
     * @return the prm
     */
    public CtrlPrm getPrm() {
        return prm;
    }

    /**
     * B.4.3.3.2.2 启动标志位PRM<br/>
     * PRM =1：表示此帧报文来自启动站； PRM =0：表示此帧报文来自从动站。<br/>
     * 
     * @param prm
     *            the prm to set
     */
    public void setPrm(CtrlPrm prm) {
        this.prm = prm;
    }

    /**
     * B.4.3.3.2.5 帧计数有效位FCV<br/>
     * FCV=1：表示 FCB 位有效； FCV=0：表示 FCB 位无效。<br/>
     * 
     * @return the fcv
     */
    public CtrlFcv getFcv() {
        return fcv;
    }

    /**
     * B.4.3.3.2.5 帧计数有效位FCV
     * FCV=1：表示 FCB 位有效； FCV=0：表示 FCB 位无效。
     * 
     * @param fcv
     *            the fcv to set
     */
    public void setFcv(CtrlFcv fcv) {
        this.fcv = fcv;
    }

    /**
     * <pre>
     * B.4.3.3.2.3 帧计数位FCB
     * 当帧计数有效位 FCV=1 时， FCB 表示每个站连续的发送/确认或者请求/响应服务的变化位。 FCB 位
     * 用来防止信息传输的丢失和重复。
     * 启动站向同一从动站传输新的发送/确认或请求/响应传输服务时，将 FCB 取相反值。启动站保存每
     * 一个从动站 FCB 值，若超时未收到从动站的报文，或接收出现差错，则启动站不改变 FCB 的状态，重
     * 复原来的发送/确认或者请求/响应服务。
     * 复位命令中的 FCB=0，从动站接收复位命令后将 FCB 置“0”。
     * </pre>
     * 
     * @return the fcb
     */
    public CtrlFcb getFcb() {
        return fcb;
    }

    /**
     * <pre>
     * B.4.3.3.2.3 帧计数位FCB
     * 当帧计数有效位 FCV=1 时， FCB 表示每个站连续的发送/确认或者请求/响应服务的变化位。 FCB 位
     * 用来防止信息传输的丢失和重复。
     * 启动站向同一从动站传输新的发送/确认或请求/响应传输服务时，将 FCB 取相反值。启动站保存每
     * 一个从动站 FCB 值，若超时未收到从动站的报文，或接收出现差错，则启动站不改变 FCB 的状态，重
     * 复原来的发送/确认或者请求/响应服务。
     * 复位命令中的 FCB=0，从动站接收复位命令后将 FCB 置“0”。
     * </pre>
     * 
     * @param fcb
     *            the fcb to set
     */
    public void setFcb(CtrlFcb fcb) {
        this.fcb = fcb;
    }

    /**
     * <pre>
     * B.4.3.3.2.4 请求访问位ACD
     * ACD 位用于上行响应报文中。 ACD=1 表示终端有重要事件等待访问，则附加信息域中带有事件计
     * 数器 EC（EC 见本附录 B.4.3.4.6.3）； ACD=0 表示终端无事件数据等待访问。
     * ACD 置“1” 和置“0” 规则：
     * ——自上次收到报文后发生新的重要事件， ACD 位置“1”；
     * ——收到主站请求事件报文并执行后， ACD 位置“0”。
     * </pre>
     * 
     * @return the acd
     */
    public CtrlAcd getAcd() {
        return acd;
    }

    /**
     * <pre>
     * B.4.3.3.2.4 请求访问位ACD
     * ACD 位用于上行响应报文中。 ACD=1 表示终端有重要事件等待访问，则附加信息域中带有事件计
     * 数器 EC（EC 见本附录 B.4.3.4.6.3）； ACD=0 表示终端无事件数据等待访问。
     * ACD 置“1” 和置“0” 规则：
     * ——自上次收到报文后发生新的重要事件， ACD 位置“1”；
     * ——收到主站请求事件报文并执行后， ACD 位置“0”。
     * </pre>
     * 
     * @param acd
     *            the acd to set
     */
    public void setAcd(CtrlAcd acd) {
        this.acd = acd;
    }

    /**
     * <pre>
     * 表B.2 功能码定义（ PRM=1）
     * 功能码 帧类型 服务功能
     * 0 —— 备用
     * 1 发送⁄确认 复位命令
     * 2～3 —— 备用
     * 4 发送⁄无回答 用户数据
     * 5～8 —— 备用
     * 9 请求⁄响应帧 链路测试
     * 10 请求⁄响应帧 请求 1 级数据
     * 11 请求⁄响应帧 请求 2 级数据
     * 12～15 —— 备用
     * </pre>
     * 
     * @return the fn
     */
    public FnActiveType getFat() {
        return fat;
    }

    /**
     * <pre>
     * B.4.3.3.2.6 功能码
     * 当启动标志位 PRM =1 时，功能码定义见表 B.2。
     * 表B.2 功能码定义（ PRM=1）
     * 功能码 帧类型 服务功能
     * 0 —— 备用
     * 1 发送⁄确认 复位命令
     * 2～3 —— 备用
     * 4 发送⁄无回答 用户数据
     * 5～8 —— 备用
     * 9 请求⁄响应帧 链路测试
     * 10 请求⁄响应帧 请求 1 级数据
     * 11 请求⁄响应帧 请求 2 级数据
     * 12～15 —— 备用
     * </pre>
     * 
     * @param fn
     *            the fn to set
     */
    public void setFat(FnActiveType fat) {
        this.fat = fat;
    }

    /**
     * <pre>
     * 当启动标志位 PRM =0 时，功能码定义见表 B.3。
     * 表B.3 功能码定义（ PRM=0）
     * 功能码 帧类型 服务功能
     * 0 确认 认可
     * 1～7 —— 备用
     * 8 响应帧 用户数据
     * 9 响应帧 否认：无所召唤的数据
     * 10 —— 备用
     * 11 响应帧 链路状态
     * 12～15 —— 备用
     * </pre>
     * 
     * @return
     */
    public FnProactiveType getPat() {
        return pat;
    }

    /**
     * <pre>
     * 当启动标志位 PRM =0 时，功能码定义见表 B.3。
     * 表B.3 功能码定义（ PRM=0）
     * 功能码 帧类型 服务功能
     * 0 确认 认可
     * 1～7 —— 备用
     * 8 响应帧 用户数据
     * 9 响应帧 否认：无所召唤的数据
     * 10 —— 备用
     * 11 响应帧 链路状态
     * 12～15 —— 备用
     * </pre>
     * 
     * @param pat
     *            the fn for proactive to set
     */
    public void setPat(FnProactiveType pat) {
        this.pat = pat;
    }

    public enum CtrlDir {
        Down(0),
        Up(1);

        private int value;

        private static Map<Integer, CtrlDir> dirMap = new HashMap<Integer, CtrlDir>();

        static {
            dirMap.put(Down.value, Down);
            dirMap.put(Up.value, Up);
        }

        private CtrlDir(int dir) {
            this.value = dir;
        }

        public static CtrlDir findDirByValue(int value) {
            if (!dirMap.containsKey(value)) {
                throw new IllegalArgumentException("dir can be 0/1.");
            }

            return dirMap.get(value);
        }

        /**
         * @return the dir
         */
        public int getValue() {
            return value;
        }
    }

    public enum CtrlPrm {
        Proactive(0),
        Active(1);

        private static Map<Integer, CtrlPrm> prmMap = new HashMap<Integer, CtrlPrm>();

        private int value;

        static {
            prmMap.put(Proactive.value, Proactive);
            prmMap.put(Active.value, Active);
        }

        private CtrlPrm(int prm) {
            this.value = prm;
        }

        public static CtrlPrm findPrmByValue(int v) {
            if (!prmMap.containsKey(v)) {
                throw new IllegalArgumentException("prm can be 0 / 1.");
            }

            return prmMap.get(v);
        }

        /**
         * @return the value
         */
        public int getValue() {
            return value;
        }

    }

    public enum CtrlAcd {

        NoEvents(0),
        HasEvents(1);

        private int value;

        private static Map<Integer, CtrlAcd> acdMap = new HashMap<Integer, CtrlAcd>();

        static {
            acdMap.put(NoEvents.value, NoEvents);
            acdMap.put(HasEvents.value, HasEvents);
        }

        private CtrlAcd(int dir) {
            this.value = dir;
        }

        public static CtrlAcd findAcdByValue(int value) {
            if (!acdMap.containsKey(value)) {
                throw new IllegalArgumentException("acd can be 0/1.");
            }

            return acdMap.get(value);
        }

        /**
         * @return the dir
         */
        public int getValue() {
            return value;
        }

    }

    public enum CtrlFcb {

        Invalid(0),
        Valid(1);

        private int value;

        private static Map<Integer, CtrlFcb> fcbMap = new HashMap<Integer, CtrlFcb>();

        static {
            fcbMap.put(Invalid.value, Invalid);
            fcbMap.put(Valid.value, Valid);
        }

        private CtrlFcb(int dir) {
            this.value = dir;
        }

        public static CtrlFcb findFcvByValue(int value) {
            if (!fcbMap.containsKey(value)) {
                throw new IllegalArgumentException("fcb can be 0/1.");
            }

            return fcbMap.get(value);
        }

        /**
         * @return the dir
         */
        public int getValue() {
            return value;
        }

    }

    public enum CtrlFcv {

        Invalid(0),
        Valid(1);

        private int value;

        private static Map<Integer, CtrlFcv> fcvMap = new HashMap<Integer, CtrlFcv>();

        static {
            fcvMap.put(Invalid.value, Invalid);
            fcvMap.put(Valid.value, Valid);
        }

        private CtrlFcv(int dir) {
            this.value = dir;
        }

        public static CtrlFcv findFcvByValue(int value) {
            if (!fcvMap.containsKey(value)) {
                throw new IllegalArgumentException("fcv can be 0/1.");
            }

            return fcvMap.get(value);
        }

        /**
         * @return the dir
         */
        public int getValue() {
            return value;
        }
    }

}
